JBoss Community Archive (Read Only)

Errai

Data Binding

Errai's data binding module provides the ability to bind model objects to UI fields/widgets. The bound properties of the model and the UI components will automatically be kept in sync for as long as they are bound. So, there is no need to write code for UI updates in response to model changes and no need to register listeners to update the model in response to UI changes.

Getting Started

The data binding module is directly integrated with Errai UI and Errai JPA but can also be used as a standalone project in any GWT client application:

Compile-time dependency

To use Errai's data binding module, you must include it on the compile-time classpath. If you are using Maven for your build, add this dependency:

    <dependency>
      <groupId>org.jboss.errai</groupId>
      <artifactId>errai-data-binding</artifactId>
      <version>${errai.version}</version>
    </dependency>

If you are not using Maven for dependency management, add errai-data-binding-version.jar to your classpath.

GWT module descriptor

You must also inherit the Errai data binding module by adding the following line to your GWT module descriptor (gwt.xml).

App.gwt.xml
<inherits name="org.jboss.errai.databinding.DataBinding" />

Bindable Objects

Objects that should participate in data bindings have to be marked as @Bindable and must follow Java bean conventions. All editable properties of these objects are then bindable to UI widgets.

Customer.java
@Bindable
public class Customer {
  ...
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
  ...
}

If you cannot or prefer not to annotate your classes with @Bindable, you can alternatively specify bindable types in your ErraiApp.properties using a whitespace-separated list of fully qualified class names: errai.ui.bindableTypes=org.example.Model1 org.example.Model2

Initializing a DataBinder

An instance of DataBinder is required to create bindings. It can either be

injected into a client-side bean:

public class CustomerView {
  @Inject
  private DataBinder<Customer> dataBinder;
}

or created manually:

DataBinder<Customer> dataBinder = DataBinder.forType(Customer.class);

In both cases above, the DataBinder instance is associated with a new instance of the model (e.g. a new Customer object). A DataBinder can also be associated with an already existing object:

DataBinder<Customer> dataBinder = DataBinder.forModel(existingCustomerObject);

In case there is existing state in either the model object or the UI components before the they are bound, initial state synchronization can be carried out to align the model and the corresponding UI fields.

For using the model object's state to set the initial values in the UI:

DataBinder<Customer> dataBinder = DataBinder.forModel(existingCustomerObject, InitialState.FROM_MODEL);

For using the UI values to set the initial state in the model object:

DataBinder<Customer> dataBinder = DataBinder.forModel(existingCustomerObject, InitialState.FROM_UI);

Creating Bindings

Bindings can be created by calling the bind method on a DataBinder instance, thereby specifying which widgets should be bound to which properties of the model. It is possible to use property chains for bindings, given that all nested properties are of bindable types. When binding to customer.address.streetName, for example, both customer and address have to be of a type annotated with @Bindable.

public class CustomerView {
  @Inject
  private DataBinder<Customer> dataBinder;

  private Customer customer;
  private TextBox nameTextBox = new TextBox();
  // more UI widgets...

  @PostConstruct
  private void init() {
    customer = dataBinder
        .bind(nameTextBox, "name")
        .bind(idLabel, "id")
        .getModel();
  }
}

After the call to dataBinder.bind() in the example above, the customer object's name property and the nameTextBox are kept in sync until either the dataBinder.unbind() method is called or the CustomerView bean is destroyed.

That means that a call to customer.setName() will automatically update the value of the TextBox and any change to the TextBox's value in the browser will update the customer object's name property. So, customer.getName() will always reflect the currently displayed value of the TextBox.

It's important to retrieve the model instance using dataBinder.getModel() before making changes to it as the data binder will provide a proxy to the model to ensure that changes will update the corresponding UI components.

Errai also provides a declarative binding API that can be used to create bindings automatically based on matching field and model property names.

Specifying Converters

Errai has built-in conversion support for all Number types as well as Boolean and Date to java.lang.String and vice versa. However, in some cases it will be necessary to provide custom converters (e.g. if a custom date format is desired). This can be done on two levels.

Registering a global default converter

@DefaultConverter
public class MyCustomDateConverter implements Converter<Date, String> {

  private static final String DATE_FORMAT = "YY_DD_MM";

  @Override
  public Date toModelValue(String widgetValue) {
    return DateTimeFormat.getFormat(DATE_FORMAT).parse(widgetValue);
  }

  @Override
  public String toWidgetValue(Date modelValue) {
    return DateTimeFormat.getFormat(DATE_FORMAT).format((Date) modelValue);
  }
}

All converters annotated with @DefaultConverter will be registered as global defaults calling Convert.registerDefaultConverter(). Note that the Converter interface specifies two type parameters. The first one represents the type of the model field, the second one the type held by the widget (e.g. String for widgets implementing HasValue<String>). These default converters will be used for all bindings with matching model and widget types.

Providing a binding-specific converter

Alternatively, converter instances can be passed to the dataBinder.bind() calls.

dataBinder.bind(textBox, "name", customConverter);

Converters specified on the binding level take precedence over global default converters with matching types.

Property Change Handlers

In some cases keeping the model and the UI in sync is not enough. Errai's DataBinder allows for the registration of PropertyChangeHandlers for specific properties, property expressions or all properties of a bound model. A property expression can be a property chain such as customer.address.street. It can end in a wildcard to indicate that changes of any property of the corresponding bean should be observed (e.g "customer.address.*"). A double wildcard can be used at the end of a property expression to register a cascading change handler for any nested property (e.g "customer.**").

This provides a uniform notification mechanism for model and UI value changes. PropertyChangeHandlers can be used to carry out any additional logic that might be necessary after a model or UI value has changed.

dataBinder.addPropertyChangeHandler(new PropertyChangeHandler() {
  @Override
  public void onPropertyChange(PropertyChangeEvent event) {
    Window.alert(event.getPropertyName() + " changed to:" + event.getNewValue());
  }
});
dataBinder.addPropertyChangeHandler("name", new PropertyChangeHandler() {
  @Override
  public void onPropertyChange(PropertyChangeEvent event) {
    Window.alert("name changed to:" + event.getNewValue());
  }
});

Declarative Binding

Programmatic binding as described above (see Creating Bindings) can be tedious when working with UI components that contain a large number of input fields. Errai provides an annotation-driven binding API that can be used to create bindings automatically which cuts a lot of boilerplate code. The declarative API will work in any Errai IOC managed bean (including Errai UI templates). Simply inject a data binder or model object and declare the bindings using @Bound.

Here is a simple example using an injected model object provided by the @Model annotation (field injection is used here, but constructor and method injection are supported as well):

@Dependent
public class CustomerView {
  @Inject @Model
  private Customer customer;

  @Inject @Bound
  private TextBox name;

  @Bound
  private Label id = new Label();

  ....
}

Here is the same example injecting a DataBinder instead of the model object. This is useful when more control is needed (e.g. the ability to register property change handlers). The @AutoBound annotation specifies that this DataBinder should be used to bind the model to all enclosing widgets annotated with @Bound. This example uses field injection again but constructor and method injection are supported as well.

@Dependent
public class CustomerView {
  @Inject @AutoBound
  private DataBinder<Customer> customerBinder;

  @Inject @Bound
  private TextBox name;

  @Bound
  private Label id = new Label();

  ...
}

In both examples above an instance of the Customer model is automatically bound to the corresponding UI widgets based on matching field names. The model object and the UI fields will automatically be kept in sync. The widgets are inferred from all enclosing fields and methods annotated with @Bound of the class that defines the @AutoBound DataBinder or @Model and all its super classes.

Default, Simple, and Chained Property Bindings

By default, bindings are determined by matching field names to property names on the model object. In the examples above, the field name was automatically bound to the JavaBeans property name of the model (user object). If the field name does not match the model property name, you can use the property attribute of the @Bound annotation to specify the name of the property. The property can be a simple name (for example, "name") or a property chain (for example, user.address.streetName). When binding to a property chain, all properties but the last in the chain must refer to @Bindable values.

The following example illustrates all three scenarios:

@Bindable
public class Address {
  private String line1;
  private String line2;
  private String city;
  private String stateProv;
  private String country;

  // getters and setters
}

@Bindable
public class User {
  private String name;
  private String password;
  private Date dob;
  private Address address;
  private List<Role> roles;

  // getters and setters
}

@Templated
public class UserWidget {
  @Inject @AutoBound DataBinder<User> user;
  @Inject @Bound TextBox name;
  @Inject @Bound("dob") DatePicker dateOfBirth;
  @Inject @Bound("address.city") TextBox city;
}

In UserWidget above, the name text box is bound to user.name using the default name matching; the dateOfBirth date picker is bound to user.dob using a simple property name mapping; finally, the city text box is bound to user.address.city using a property chain. Note that the Address class is required to be @Bindable in this case.

Data Converters

The @Bound annotation further allows to specify a converter to use for the binding (see Specifying Converters for details). This is how a binding specific converter can be specified on a data field:

@Inject
@Bound(converter=MyDateConverter.class)
@DataField
private TextBox date;

Replacing a model object

The injected model objects in the examples above are always proxies to the actual model since method invocations on these objects need to trigger additional logic for updating the UI. Special care needs to be taken in case a model object should be replaced.

When working with an @AutoBound DataBinder, simply calling setModel() on the DataBinder will be enough to replace the underlying model instance. However, when working with @Model the instance cannot be replaced directly. Errai provides a special method level annotation @ModelSetter that will allow replacing the model instance. Here's an example:

@Dependent
public class CustomerView {
  @Inject @Model
  private Customer customer;

  @Inject @Bound
  private TextBox name;

  @Bound
  private Label id = new Label();

  @ModelSetter
  public void setModel(Customer customer) {
    this.customer = customer;
  }
}

The @ModelSetter method is required to have a single parameter. The parameter type needs to correspond to the type of the managed model.

Bean validation

Java bean validation (JSR 303) provides a declarative programming model for validating entities. More details and examples can be found here. Errai provides a bean validation module that makes Validator instances injectable and work well with Errai's data binding module. The following line needs to be added to the GWT module descriptor to inherit Errai's bean validation module:

App.gwt.xml
<inherits name="org.jboss.errai.validation.Validation" />

<inherits name="org.hibernate.validator.HibernateValidator" />

To use Errai's bean validation module, you must add the module, the javax.validation API and an implementation such as hibernate validator to your classpath. If you are using Maven for your build, add these dependencies:

    <dependency>
      <groupId>org.jboss.errai</groupId>
      <artifactId>errai-validation</artifactId>
      <version>${errai.version}</version>
    </dependency>

    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <classifier>sources</classifier>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>4.2.0.Final</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>4.2.0.Final</version>
      <scope>provided</scope>
      <classifier>sources</classifier>
    </dependency>

Now it is as simple as injecting a Validator instance into an Errai IOC managed bean and calling the validate method.

@Inject
private Validator validator;
Set<ConstraintViolation<Customer>> violations  = validator.validate(customer);
// display violations

Excluding Classes from Validation

By default, Errai scans the entire classpath for classes with constraints. But sometimes it is necessary or desirable to exclude some shared classes from being validated on the client side. This can be done by adding a list of classes and package masks to the ErraiApp.properties file like so:

# The following blacklists the class some.fully.qualified.ClassName and all classes in some.package.mask (and subpackages thereof).
errai.validation.blacklist = some.fully.qualified.ClassName \
                             some.package.mask.*
JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-10 12:34:55 UTC, last content change 2013-12-06 19:31:44 UTC.